/**
 * 
 */
package persistence;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.LinkedList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * @author fabiensaulnier
 * 
 */
public class PersistenceSession {

	private static Logger log = LoggerFactory.getLogger(PersistenceSession.class);

	private C3p0Configuration c3p0Configuration;

	private PersistenceConfiguration persistenceConfiguration;

	private Connection connection;

	public PersistenceSession(PersistenceConfiguration persistenceConfiguration) throws PersistenceException {
		this.persistenceConfiguration = persistenceConfiguration;
		this.initConnection();
	}

	private void initConnection() throws PersistenceException {

		String driverClass = this.persistenceConfiguration.getConnectionConfiguration().getDriverClass();
		String jdbcURL = this.persistenceConfiguration.getConnectionConfiguration().getUrl();
		String dbUser = this.persistenceConfiguration.getConnectionConfiguration().getUserName();
		String dbPassword = this.persistenceConfiguration.getConnectionConfiguration().getPassword();

		try {

			c3p0Configuration = this.persistenceConfiguration.getConnectionConfiguration().getC3p0Configuration();
			if (c3p0Configuration != null) {
				this.connectionWithPool(driverClass, jdbcURL, dbUser, dbPassword);
			} else {
				this.connectionWithoutPool(driverClass, jdbcURL, dbUser, dbPassword);
			}

		} catch (Exception e) {
			throw new PersistenceException(e.getMessage());
		}
	}

	private void connectionWithPool(String driverClass, String jdbcURL, String dbUser, String dbPassword) throws Exception {

		log.debug("Connection avec pool");
		ComboPooledDataSource cpds = new ComboPooledDataSource();
		cpds.setDriverClass(driverClass);
		cpds.setJdbcUrl(jdbcURL);
		cpds.setUser(dbUser);
		cpds.setPassword(dbPassword);
		cpds.setMaxStatements(this.c3p0Configuration.getMaxStatements());
		cpds.setMinPoolSize(this.c3p0Configuration.getMinSize());
		cpds.setMaxPoolSize(this.c3p0Configuration.getMaxSize());
		// Pose problème pour le mode DEBUG
		// cpds.setCheckoutTimeout(this.c3p0Configuration.getTimeout());

		connection = cpds.getConnection();

	}

	private void connectionWithoutPool(String driverClass, String jdbcURL, String dbUser, String dbPassword) throws Exception {

		log.debug("Connection sans pool (classique)");
		Class.forName(driverClass);
		connection = DriverManager.getConnection(jdbcURL, dbUser, dbPassword);

	}

	/**
	 * @param clazz
	 * @throws PersistenceException
	 */
	public void createTable(Class<?> clazz) throws PersistenceException {

		try {

			// Récupération de la classe de l'objet & de la pc correspondante
			PersistentClass pc = this.persistenceConfiguration.getPersistentClass(clazz);
			log.debug("Recupereration de la class [" + clazz + "]");

			// Préparation de la requête
			PreparedStatement preparedStatement = connection.prepareStatement(pc.getCreateTableStatement());
			log.debug("Execution de la requete [" + pc.getCreateTableStatement() + "]");

			// On execute la requête
			preparedStatement.execute();
			log.info("Table [" + pc.getName() + "] cree");

		} catch (Exception e) {
			log.error("Erreur lors de la creation de la table de la table", e);
			throw new PersistenceException(e.getMessage());
		}

	}

	/**
	 * @param clazz
	 * @throws PersistenceException
	 */
	public void dropTable(Class<?> clazz) throws PersistenceException {

		try {

			// Récupération de la classe de l'objet & de la pc correspondante
			PersistentClass pc = this.persistenceConfiguration.getPersistentClass(clazz);
			log.debug("Récupération de la class [" + clazz + "]");

			// Préparation de la requête
			PreparedStatement preparedStatement = connection.prepareStatement(pc.getDropTableStatement());
			log.debug("Exécution de la requête [" + pc.getDropTableStatement() + "]");

			// Exécution de la requête
			preparedStatement.execute();
			log.info("Table [" + pc.getName() + "] supprimee");

		} catch (Exception e) {
			log.error("Erreur lors de la suppression de la table", e);
			throw new PersistenceException("Erreur lors de la suppression de la table" + e.getMessage());
		}
	}

	/**
	 * @param bean
	 * @throws PersistenceException
	 * @throws SQLException
	 */
	public void save(Object bean) throws PersistenceException {

		try {

			// Récupération de la classe de l'objet & de la pc correspondante
			PersistentClass pc = this.persistenceConfiguration.getPersistentClass(bean.getClass());
			log.debug("Récupération de la class [" + bean.getClass() + "]");

			// Préparation de la requête
			PreparedStatement preparedStatement = connection.prepareStatement(pc.getInsertStatement(), Statement.RETURN_GENERATED_KEYS);
			log.debug("Exécution de la requête [" + pc.getInsertStatement() + "]");

			int i = 1;
			for (PersistentProperty pp : pc.getPersistentProperties()) {
				if (pp.isId()) {
					continue;
				}

				Object o = pp.getValue(bean);
				if (o == null) {
					preparedStatement.setNull(i, Types.BIGINT);
				} else {
					preparedStatement.setObject(i, o);
					log.debug("> " + i + " : " + o);
				}
				i++;
			}

			// Exécution de la requête et traite le résultat (ici id uniquement)
			preparedStatement.execute();
			ResultSet rs = preparedStatement.getGeneratedKeys();
			if (rs.next()) {
				int id = rs.getInt(1);
				pc.getPersistentProperty("id").setValue(bean, id);
				pc.getId().setValue(bean, id);
			}

		} catch (Exception e) {
			log.error("Une erreur est survenue dans le save : " + e);
			e.printStackTrace();
			throw new PersistenceException("Une erreur est survenue dans le save : " + e.getMessage());
		}

	}

	/**
	 * @param clazz
	 * @param id
	 * @return
	 * @throws PersistenceException
	 */
	public Object get(Class<?> clazz, Object id) throws PersistenceException {

		try {

			// Récupération de la classe de l'objet & de la pc correspondante
			PersistentClass pc = this.persistenceConfiguration.getPersistentClass(clazz);
			log.debug("Récupération de la class [" + clazz + "]");

			Object bean = pc;

			// Préparation de la requête
			PreparedStatement preparedStatement = connection.prepareStatement(pc.getSelectByIdStatement(), Statement.RETURN_GENERATED_KEYS);
			log.debug("Execution de la requete  [" + pc.getSelectByIdStatement() + "] ");
			preparedStatement.setObject(1, id);

			// Exécution de la requête et traitement le résultat
			preparedStatement.executeQuery();

			ResultSet rs = preparedStatement.getResultSet();
			if (rs.next()) {
				bean = pc.createInstance();
				int i = 1;
				for (PersistentProperty pp : pc.getPersistentProperties()) {
					pp.setValue(bean, rs.getObject(i));
					i++;
				}
				log.debug("Resultat de la requete(get) : " + bean);
				return bean;
			}
		} catch (Exception e) {
			log.error("Une erreur est survenue sur le get : " + e);
			throw new PersistenceException("Une erreur est survenue sur le get : " + e.getMessage());
		}

		return null;
	}

	public void update(Object bean) throws PersistenceException {

		try {
			log.debug("Récupération de la class [" + bean.getClass() + "]");
			// Récupération de la classe de l'objet & de la pc correspondante
			PersistentClass pc = this.persistenceConfiguration.getPersistentClass(bean.getClass());

			// Préparation de la requête
			PreparedStatement preparedStatement = connection.prepareStatement(pc.getUpdateStatement());
			log.debug("Exécution de la requête [" + pc.getUpdateStatement() + "]");

			// Gestion du paramètres des attributs modifiés
			int i = 1;
			for (PersistentProperty pp : pc.getPersistentProperties()) {
				if (pp.isId()) {
					continue;
				}
				Object o = pp.getValue(bean);
				if (o == null) {
					preparedStatement.setNull(i, Types.BIGINT);
				} else {
					preparedStatement.setObject(i, o);
					log.debug(">" + i + " : " + o);
				}
				i++;
			}

			// Gestion du paramètre de la clause WHERE id = ?
			preparedStatement.setObject(i, pc.getId().getValue(bean));

			// Exécution de la requête, ici inutile de traiter le résultat
			preparedStatement.executeUpdate();

		} catch (Exception e) {
			log.error("Une erreur est survenue sur le update : " + e);
			e.printStackTrace();
			throw new PersistenceException("Une erreur est survenue sur le update : " + e);
		}
	}

	public void delete(Object bean) throws PersistenceException {

		try {

			// Récupération de la classe de l'objet & de la pc correspondante
			PersistentClass pc = this.persistenceConfiguration.getPersistentClass(bean.getClass());
			log.debug("Récupération de la class [" + bean.getClass() + "]");

			// Préparation de la requête
			PreparedStatement preparedStatement = connection.prepareStatement(pc.getDeleteStatement(), Statement.RETURN_GENERATED_KEYS);

			// Gestion du paramètre de la clause WHERE id = ?
			Object id = pc.getPersistentProperty("id").getValue(bean);
			preparedStatement.setObject(1, id);

			// Exécution de la requête, ici inutile de traiter le résultat
			preparedStatement.execute();

		} catch (Exception e) {
			log.error("Une erreur est survenue sur le delete : " + e);
			throw new PersistenceException("Une erreur est survenue sur le delete : " + e);
		}

	}

	/**
	 * Fermeture de la connexion et du preparedStatement
	 * 
	 * @throws SQLException
	 */
	public void close() throws SQLException {
		if (connection != null) {
			connection.close();
			// connection = null;
		}
	}

	/**
	 * @param clazz
	 * @param query
	 * @return
	 * @throws PersistenceException
	 */
	public List<?> query(Class<?> clazz, String query) throws PersistenceException {

		List<Object> result = new LinkedList<Object>();
		Object bean;

		try {

			// Récupération de la classe de l'objet & de la pc correspondante
			PersistentClass pc = this.persistenceConfiguration.getPersistentClass(clazz);
			log.debug("Récupération de la class [" + clazz + "]");

			// Préparation de la requête
			PreparedStatement preparedStatement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
			log.debug("Exécution de la requête [" + query + "]");

			// Exécution de la requête et traite le résultat
			preparedStatement.execute();
			ResultSet rs = preparedStatement.getResultSet();
			if (rs.next()) {
				bean = pc.createInstance();
				int i = 1;
				for (PersistentProperty pp : pc.getPersistentProperties()) {
					pp.setValue(bean, rs.getObject(i));
					i++;
				}
				result.add(bean);
				log.debug(result.toString());
			}

		} catch (Exception e) {
			log.error("Une erreur est survenue sur le query : " + e);
			throw new PersistenceException("Une erreur est survenue sur le query : " + e);
		}

		return result;
	}

}
